package hn.sigit.logic.workflow.transaction.impl;

import hn.sigit.dao.SigitDAO;
import hn.sigit.dao.hnd.administrative.HND_MunicipalTransactionDAO;
import hn.sigit.dao.hnd.cadastre.HND_ParcelDAO;
import hn.sigit.dao.ladm.external.ExtPartyDAO;
import hn.sigit.logic.bpm.ArchiveRepository;
import hn.sigit.logic.bpm.AttachedFileData;
import hn.sigit.logic.ladmshadow.Util;
import hn.sigit.logic.queries.TransactionQueryHelper;
import hn.sigit.logic.workflow.WorkflowHelperBase;
import hn.sigit.logic.workflow.transaction.TransactionHelper;
import hn.sigit.model.commons.ISpatialZone;
import hn.sigit.model.hnd.administrative.HND_ActivityType;
import hn.sigit.model.hnd.administrative.HND_MunicipalTransaction;
import hn.sigit.model.hnd.administrative.HND_MunicipalTransactionType;
import hn.sigit.model.hnd.administrative.HND_SpatialZoneInTransaction;
import hn.sigit.model.hnd.administrative.HND_TransactionSubType;
import hn.sigit.model.hnd.cadastre.HND_Parcel;
import hn.sigit.model.hnd.cadastre.HND_SpatialZone;
import hn.sigit.model.hnd.special.HND_Lock;
import hn.sigit.model.ladm.administrative.LA_AdministrativeSource;
import hn.sigit.model.ladm.external.ExtParty;
import hn.sigit.util.DataValidationException;
import hn.sigit.util.LockedException;
import hn.sigit.util.SigitException;

import java.util.ArrayList;
import java.util.Date;
import java.util.HashSet;
import java.util.List;
import java.util.Set;

import javax.ejb.Remove;
import javax.faces.application.FacesMessage;
import javax.faces.context.FacesContext;
import javax.faces.model.SelectItem;

import org.jboss.seam.Component;
import org.jboss.seam.ScopeType;
import org.jboss.seam.annotations.AutoCreate;
import org.jboss.seam.annotations.Begin;
import org.jboss.seam.annotations.Destroy;
import org.jboss.seam.annotations.End;
import org.jboss.seam.annotations.Name;
import org.jboss.seam.annotations.Scope;
import org.jboss.seam.annotations.Transactional;
import org.jboss.seam.annotations.bpm.BeginTask;
import org.jboss.seam.annotations.bpm.CreateProcess;
import org.jboss.seam.annotations.bpm.EndTask;
import org.jboss.seam.bpm.BusinessProcess;
import org.jboss.seam.bpm.ProcessInstance;
import org.jbpm.taskmgmt.exe.TaskInstance;


@Name(TransactionHelper.NAME)
@Scope(value=ScopeType.CONVERSATION)
@AutoCreate
public class TransactionHelperImpl extends WorkflowHelperBase implements TransactionHelper {
	private static final long serialVersionUID = 1L;

	private HND_MunicipalTransaction transaction;
	private Long transactionId;
	
	private ExtParty party;
	private ExtParty favoredParty;
	
	private boolean attachInfo;
	
	private Set<HND_SpatialZone> requestedZoneSet;
	private List<HND_SpatialZone> requestedZoneList;
	
	private TransactionLogicHelper transactionLogicHelper = new TransactionLogicHelper(this);
	private HND_MunicipalTransactionType requestType;
	private HND_TransactionSubType requestSubType;
	private List<SelectItem> requestSubTypes;

	
	@Override
	public HND_MunicipalTransaction getTransaction() {
		if (transaction == null) {
			if (transactionId == null)
				transaction = new HND_MunicipalTransaction();
			else
				transaction = HND_MunicipalTransactionDAO.loadRequestByPresentationId(transactionId);
		}
		return transaction;
	}
	@Override
	public void setTransaction(HND_MunicipalTransaction transaction) {
		this.transaction = transaction;
	}


	@Override
	public ExtParty getParty() {
		return party;
	}
	@Override
	public void setParty(ExtParty party) {
		this.party = party;
	}

	@Override
	public ExtParty getFavoredParty() {
		return favoredParty;
	}
	@Override
	public void setFavoredParty(ExtParty favoredParty) {
		this.favoredParty = favoredParty;
	}
	@Override
	public Long getTransactionId() {
		return transactionId;
	}
	@Override
	public void setTransactionId(Long transactionId) {
		this.transactionId = transactionId;
	}
	
	@Override
	public Long getPartyId() {
		if (party != null)
			return party.getExtPID(); 
		return null;
	}
	
	@Override
	public String getPartyFormalIdentity() {
		if (party != null)
			return party.getFormalIdentity();
		return "";
	}
	
	@Override
	public String getPartyName() {
		if (party != null)
			return party.getName();
		return "";
	}
	
	@Override
	public Long getFavoredPartyId() {
		if (favoredParty != null)
			return favoredParty.getExtPID(); 
		return null;
	}
	
	@Override
	public String getFavoredPartyFormalIdentity() {
		if (favoredParty != null)
			return favoredParty.getFormalIdentity();
		return "";
	}
	
	@Override
	public String getFavoredPartyName() {
		if (favoredParty != null)
			return favoredParty.getName();
		return "";
	}

	@Override
	public Set<HND_SpatialZone> getRequestedZoneSet() {
		if (requestedZoneSet == null) {
			requestedZoneSet = new HashSet<HND_SpatialZone>();
			if (getTransaction().getSpatialZoneInTransactions().size() > 0)
				for (HND_SpatialZoneInTransaction szit : getTransaction().getSpatialZoneInTransactions())
					if (szit.isUserRequested())
						requestedZoneSet.add(szit.getSpatialZone());
		}
		return requestedZoneSet;
	}
	@Override
	public void setRequestedZoneSet(Set<HND_SpatialZone> requestedZoneSet) {
		this.requestedZoneSet = requestedZoneSet;
	}
	
	@Override
	public List<HND_SpatialZone> getRequestedZoneList() {
		if (requestedZoneList == null) {
			requestedZoneList = new ArrayList<HND_SpatialZone>();
			requestedZoneList.addAll(getRequestedZoneSet());
		}
		return requestedZoneList;
	}
	@Override
	public void setRequestedZoneList(List<HND_SpatialZone> requestedZoneList) {
		this.requestedZoneList = requestedZoneList;
	}
	
	/*
	public void addParcelData() {
		if (selectedParcelData != null) {
			if (!getParcelDataList().contains(selectedParcelData))
				getParcelDataList().add(selectedParcelData);
		}
	}
	public String deleteSelectedParcelData() {
		parcelDataList.remove(currentRow);
		return null;
	}
	*/
	
	@Override
	public List<LA_AdministrativeSource> getFileSources() {
		if (fileSources == null) {
			//TODO: we could add an ordering criterion
			fileSources = new ArrayList<LA_AdministrativeSource>();
			for (LA_AdministrativeSource source : transaction.getSources())
				fileSources.add(source);
		}
		
		return fileSources;
	}
	
	@Override
	public void acceptSelectedFile() {
		if (transaction != null && transaction.getPresentationId() == null) {
			if ("0".equals(editMode)) { //agregando
				if (null == attachedFiles)
					attachedFiles = new ArrayList<AttachedFileData>();
				attachedFiles.add(selectedFile);
			}
			else if ("1".equals(editMode)) { //editando
				attachedFiles.set(currentRow, selectedFile);
			}
		}
		else if (transaction != null) {
			transaction.getSources().add(ArchiveRepository.addToRepository(appOptions, selectedFile));
			HND_MunicipalTransactionDAO.save(transaction);
		}
		
		setSelectedFile(null);
		fileSources = null;
	}
	
	@Override
	public boolean isAttachInfo() {
		return attachInfo;
	}
	@Override
	public void setAttachInfo(boolean attachInfo) {
		this.attachInfo = attachInfo;
	}

	@Override
	public String hasAttachedFiles() {
		if (attachInfo)
			return "si_adjunta";
		
		return "no_adjunta";
	}
	
	private void setPresentationData() {
		HND_MunicipalTransaction hndTransaction = getTransaction();
		
		ExtParty extParty = ExtPartyDAO.loadExtPartyByExtPID(getPartyId());
		
		
		//Phase 1: Set the general data
		hndTransaction.setCurrentActivity(HND_ActivityType.PRESENTATION);
		hndTransaction.setPresentationDate(new Date());
		hndTransaction.setExtParty(extParty);
		hndTransaction.setReceptionistUserName(actor.getId());
		hndTransaction.setReceptionistFullName(loggedUserFullName);
		
		//Phase 2: Set attached files data
		if (getAttachedFiles() != null) {
			Set<LA_AdministrativeSource> adminSources = hndTransaction.getSources();
			if (adminSources == null) {
				adminSources = new HashSet<LA_AdministrativeSource>();
				hndTransaction.setSources(adminSources);
			}
			
			for (AttachedFileData afd : attachedFiles) {
				adminSources.add(ArchiveRepository.addToRepository(appOptions, afd));
			}
		}
		
		//Phase 3: Save transaction
		HND_MunicipalTransactionDAO.save(hndTransaction);
		HND_MunicipalTransactionDAO.flush();
		HND_MunicipalTransactionDAO.refresh(hndTransaction);
		setTransactionId(hndTransaction.getPresentationId());
	}

	private void setParcelDataToTransaction(HND_MunicipalTransaction hndTransaction) {
		HND_SpatialZoneInTransaction newSpatialZoneInTransaction;
		Set<HND_SpatialZone> nearParcelSet = new HashSet<HND_SpatialZone>();
		Set<HND_SpatialZone> completeSpatialZoneSet = new HashSet<HND_SpatialZone>();
		for (HND_SpatialZone reqSpatialZone : getRequestedZoneSet()) {
			for (HND_SpatialZone neighborParcel : HND_ParcelDAO.loadNeighborsByHNDParcel((HND_Parcel)reqSpatialZone)) {
				nearParcelSet.add(neighborParcel);
				nearParcelSet.addAll(HND_ParcelDAO.loadNeighborsByHNDParcel((HND_Parcel)neighborParcel));
			}
		}
		completeSpatialZoneSet.addAll(requestedZoneSet);
		completeSpatialZoneSet.addAll(nearParcelSet);
		
		//synchronize parcel in transactions with the complete parcel set
		//it involves adding and deleting from parcel-in-transactions set
		
		//1: Delete from parcel-in-transaction set the parcels
		//		not in the complete-parcel set
		Set<HND_SpatialZoneInTransaction> szitToDelete = new HashSet<HND_SpatialZoneInTransaction>();
		for (HND_SpatialZoneInTransaction szit : transaction.getSpatialZoneInTransactions()) {
			if (!completeSpatialZoneSet.contains(szit.getSpatialZone()))
				szitToDelete.add(szit);
		}
		for (HND_SpatialZoneInTransaction szit : szitToDelete) {
			transaction.getSpatialZoneInTransactions().remove(szit);
			szit.getSpatialZone().getSpatialZoneInTransactions().remove(szit);
			SigitDAO.delete(szit);
		}
		
		//2: Add to parcel-in-transaction set the parcels
		//		present in the complete-parcel set
		int neighborLevel;
		for (HND_SpatialZone hndSpatialZone : completeSpatialZoneSet) {
			if (hndSpatialZone != null && !isSpatialZoneInTransaction(hndSpatialZone, hndTransaction)) {
				newSpatialZoneInTransaction = new HND_SpatialZoneInTransaction();
				newSpatialZoneInTransaction.setTransaction(hndTransaction);
				newSpatialZoneInTransaction.setSpatialZone(hndSpatialZone);
				
				if (requestedZoneSet.contains(hndSpatialZone))
					neighborLevel = 0;
				else {
					neighborLevel = 1;
					for (HND_SpatialZone reqSpatialZone : requestedZoneSet)
						if (!hndSpatialZone.getShape().intersects(reqSpatialZone.getShape()))
							neighborLevel = 2;
				}
				
				newSpatialZoneInTransaction.setNeighborLevel(neighborLevel);
				
				hndTransaction.getSpatialZoneInTransactions().add(newSpatialZoneInTransaction);
				hndSpatialZone.getSpatialZoneInTransactions().add(newSpatialZoneInTransaction);
			}
		}
	}
	
	private boolean isSpatialZoneInTransaction(ISpatialZone spatialZone, HND_MunicipalTransaction transaction) {
		for (HND_SpatialZoneInTransaction pit : transaction.getSpatialZoneInTransactions()) {
			if (pit.getSpatialZone().getSuID() == spatialZone.getSuID())
				return true;
		}
		
		return false;
	}

	private void setAnalysisData() {
		getTransaction().setAnalystUserName(actor.getId());
		getTransaction().setAnalystFullName(loggedUserFullName);
	}

	private void setDataEntryData() {
		getTransaction().setEditorUserName(actor.getId());
		getTransaction().setEditorFullName(loggedUserFullName);
	}
	
	private void setApprovalData(boolean denyTransaction) {
		getTransaction().setApproverUserName(actor.getId());
		getTransaction().setApproverFullName(loggedUserFullName);
		
		if (denyTransaction)
			getTransaction().setApproved(false);
	}

	
	@Override
	@End
	@CreateProcess(definition=TRANSACTION_PROCESS, processKey="#{transactionHelper.transactionId}")
	@Transactional
	public String beginProcess() {
		setPresentationData();
		
		return "activities";
	}
	
	
	@Override
	public List<TaskInstance> getTaskInstanceList() {
		return getTaskInstanceList(TRANSACTION_PROCESS);
	}
	@Override
	public List<TaskInstance> getPooledTaskInstanceList() {
		return getPooledTaskInstanceList(TRANSACTION_PROCESS);
	}


	@Override
	@BeginTask
	@Begin(join=true)
	public String startTask() {
		String taskName = org.jboss.seam.bpm.TaskInstance.instance().getTask().getName();
		setTransaction(HND_MunicipalTransactionDAO.loadRequestByPresentationId(
				Long.parseLong(ProcessInstance.instance().getKey())));
		
		setTransactionId(this.transaction.getPresentationId());
		setParty(this.transaction.getExtParty());
		
		return taskName;
	}
	@Override
	@EndTask
	public String endTask() {
		return "home";
	}

	@Override
	@EndTask
	public String endPresentation() {
		setPresentationData();
		return endTask();
	}
	
	@Override
	//@EndTask(transition="analysis_ok")
	public String endAnalysisOK() throws SigitException {
		try {
			//1: Set analysis step specific data. These are analyst username and full name
			setAnalysisData();
	
			//2: Set the parcel data, neighbors and neighbor's neighbors in spatial zones in transaction list
			setParcelDataToTransaction(getTransaction());
			
			//2.5: Validates that a classification was chosen, at least 1 was zone was selected, dictamen 
			//and observations were filled
			checkAnalysisData();
			
	
			//3: validates that none of the spatial units are locked
			checkNoLockedSZInTransaction();
	
			List<Long> parcelIdList = new ArrayList<Long>();
			for (HND_SpatialZoneInTransaction szit : getTransaction().getSpatialZoneInTransactions()) {
				if (szit.isUserRequested())
					parcelIdList.add(szit.getSpatialZone().getSuID());
				
				//lock the spatial zone
				szit.getSpatialZone().getLock().setLockingTransaction(getTransaction());
				szit.getSpatialZone().getLock().setLockLevel(szit.getNeighborLevel());
				
				SigitDAO.save(szit.getSpatialZone());
			}
				
			Util.copyParcelDataWithRelatedParcels( transactionId, parcelIdList, true, true );
			
			BusinessProcess.instance().endTask("analysis_ok");

			return endTask();
		}
		catch (SigitException e) {
			FacesContext.getCurrentInstance().addMessage("",
					new FacesMessage(FacesMessage.SEVERITY_ERROR,
							interactiveViewerHelper.getResBundle().loadMessage(e.getExceptionBundle()),
							e.getMessage()));
		}
		
		return null;
	}
	private void checkAnalysisData() throws SigitException {
		if (getTransaction().getRequestType() == null)
			throw new DataValidationException(
					getResBundle().loadMessage("analysis.validation.no_classification"));
		
		if (getTransaction().getSpatialZoneInTransactions().size() == 0)
			throw new DataValidationException(
					getResBundle().loadMessage("analysis.validation.no_spatialzones"));
	}
	private void checkNoLockedSZInTransaction() throws SigitException {
		HND_Lock hndLock;
		for (HND_SpatialZoneInTransaction szit : getTransaction().getSpatialZoneInTransactions()) {
			hndLock = szit.getSpatialZone().getLock();
			if (hndLock.isLocked())
				throw new LockedException(
						generalHelper.formatText(getResBundle().loadMessage("fmt.lock.detail"),
								generalHelper.getZoneUsefulName(szit.getSpatialZone()),
								hndLock.getLockingTransaction().getPresentationId()));
		}
	}
	
	@Override
	@EndTask(transition="analysis_not_ok")
	public String endAnalysisDenied() {
		setAnalysisData();
		return endTask();
	}
	
	@Override
	@EndTask(transition="dataentry_ok")
	public String endDataEntry() {
		setDataEntryData();
		
		return endTask();
	}
	
	@Override
	@EndTask(transition="dataentry_back2analysis")
	public String dataEntryBackToAnalysis() {
		return endTask();
	}

	@Override
	//@EndTask(transition="approval_ok")
	public String endApproval() {
		setApprovalData(false);
		
		BusinessProcess.instance().endTask("approval_ok");
		
		SigitDAO.flush();

		TransactionQueryHelper tqh = (TransactionQueryHelper) Component.getInstance(TransactionQueryHelper.NAME);
		if (tqh != null) {
			tqh.setSelected(getTransaction());
			return "/queries/transactions.xhtml";
		}
		return endTask();
	}
	
	@Override
	@EndTask(transition="approval_denied")
	public String endApprovalDenied() {
		setApprovalData(true);
		
		return endTask();
	}

	@Override
	public String endProcess(boolean approved, String responseData) {
		//unlock the spatial zones
		for (HND_SpatialZoneInTransaction szit : getTransaction().getSpatialZoneInTransactions()) {
			szit.getSpatialZone().getLock().unlock();
			SigitDAO.save(szit.getSpatialZone());
		}
		transactionLogicHelper.applyPresentationChanges(true, responseData);

		return null;
	}
	
	@Override
	@EndTask(transition="approval_back2analysis")
	public String approvalBackToAnalysis() {
		return endTask();
	}
	
	@Override
	@EndTask(transition="approval_back2dataentry")
	public String approvalBackToDataEntry() {
		return endTask();
	}
	
	
	@Override
	public HND_MunicipalTransactionType getRequestType() {
		return getTransaction().getRequestType();
	}
	@Override
	public void setRequestType(HND_MunicipalTransactionType requestType) {
		if (this.requestType != requestType) {
			this.requestType = requestType;
			getTransaction().setRequestType(requestType);
			requestSubTypes = null;
		}
	}
	
	
	@Override
	public HND_TransactionSubType getRequestSubType() {
		return getTransaction().getRequestSubType();
	}
	@Override
	public void setRequestSubType(HND_TransactionSubType requestSubType) {
		if (this.requestSubType != requestSubType) {
			this.requestSubType = requestSubType;
			getTransaction().setRequestSubType(requestSubType);
		}
	}
	@Override
	public List<SelectItem> getRequestSubTypes() {
		//TODO: Eliminar mas adelante si no se ocupa
		if (requestSubTypes == null) {
			requestSubTypes = new ArrayList<SelectItem>();
		}
		return requestSubTypes;
	}

	
	@Override
	public String selectZonesFromMap() {
		if (getTransaction() != null) {
			getRequestedZoneSet().addAll(interactiveViewerHelper.getHighlightedSpatialZoneList());
			setRequestedZoneList(null);
		}
		
		return null;
	}

	@Override
	public String excludeSelectedZone(HND_SpatialZone zone) {
		getRequestedZoneSet().remove(zone);
		setRequestedZoneList(null);
		return null;
	}

	@Destroy
	@Remove
	public void destroy() {
	}
}
